package campus.aiit.ssl.manage.modbus;

import campus.aiit.ssl.manage.modbus.provider.misc.ByteUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * communicate with device by socket
 *
 * @author ihaokun
 * @date 2019/12/17 11:43
 */
@Component
@Slf4j
public class SocketProcess {
  /** Socket Data Unit, SDU(host, port, ADU(address, PDU(functionCode, Data), Error Check)) */
  @Getter@Setter
  public static class SDU{
    private String host;
    private int port;
    private ModBus.ADU adu;

    public SDU() {}
    public SDU(String host, int port) {
      this.host = host;
      this.port = port;
    }
  }

  public List<Integer> procedure(SDU sdu){
    Socket session = connect(sdu.getHost(), sdu.getPort());
    pushMessage(sdu);
    return null;
  }
  /** Socket建立连接 */
  private Socket connect(String host, int port){
    Socket socket = new Socket();
    SocketAddress socketAddress = new InetSocketAddress(host, port);
    try {
      socket.connect(socketAddress, 2000); // 连不上的?毫秒断掉连接
      return socket;
    } catch (IOException e) {
      log.error("连接失败");
      e.printStackTrace();
      return null;
    }
  }
  /** Socket发布消息 */
  public boolean pushMessage(SDU sdu){
    Socket socket = connect(sdu.getHost(), sdu.getPort());
    Objects.requireNonNull(socket);
    /* 发布消息 */
    String message = sdu.getAdu().toString();
    byte[] writeBytes = ByteUtils.hexStringToBytes(message);
    try {
      OutputStream os = socket.getOutputStream();
      os.write(writeBytes);
    } catch (IOException e) {
      log.error("socket getOutputStream/write exception");
      e.printStackTrace();
    }
    if (message.startsWith("00")) { // 为广播发布，无应答（地址码为00）
      log.debug("Additional Address is 00, no response");
    } else {
      int code = Integer.parseInt(sdu.getAdu().getPdu().getFunCode());
      for (ModBus.ADU.PDU.FunctionCodes value : ModBus.ADU.PDU.FunctionCodes.values())
        if (value.hex == code) return receiveMessage(value, socket);
    }
    return true;
  }

  private boolean receiveMessage(ModBus.ADU.PDU.FunctionCodes code, Socket socket){
    try {
      List<Integer> bytes = new ArrayList<>();
      socket.setSoTimeout(2000); // 设置socket读超时时间?毫秒
      DataInputStream dis = new DataInputStream(socket.getInputStream());
      // Response ADU: address(1 byte)+funCode(1 byte)+validLength(1 byte)+data
      bytes.add(dis.readUnsignedByte());  // address
      bytes.add(dis.readUnsignedByte());  // funCode
      // Response Exception Function Code
      if (ModBus.ADU.PDU.FunctionCodes.isException(bytes.get(1))) {
        throw new ModBus.ADU.PDU.FunctionCodes.ModBusFunctionCodeException();
      }
      switch (code) {
        case READ_HOLDING_REGISTERS:
          bytes.add(dis.readUnsignedByte());  // valid length
          int[] ints = new int[bytes.get(2)];
          for (int i = 0; i < bytes.get(2); i++) bytes.add(dis.readUnsignedByte());

          return parseReadStatusResponse(ints);
        case WRITE_SINGLE_REGISTER:
          // write single register, response non-null is success
          // test result: some response ADU = request ADU
          return true;
        default:
          return true;
      }
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    }

  }
  private boolean parseReadStatusResponse(int[] ints){
    return ints[1] == 1;
  }
  private boolean parseWriteResponse(int[] ints){
    return false;
  }

}